package gedis

import (
	"crypto/tls"
	"github.com/gomodule/redigo/redis"
	"time"
)

const (
	defaultHost                = "localhost"
	defaultPort                = 6379
	defaultString              = ""
	defaultPassword            = ""
	defaultDatabase            = 0
	defaultNetwork             = "tcp"
	defaultConnectTimeout      = time.Second
	defaultWriteTimeout        = time.Second
	defaultReadTimeout         = time.Second
	defaultIdleTimeout         = 240 * time.Second
	defaultMaxIdle             = 100
	defaultMaxActive           = 10000
	defaultTestOnBorrowTimeout = time.Minute

	addressFormat = "%s:%d"

	PING_COMMAND = "PING"
)

// Options contains redis options
type Options struct {
	ClientName     string
	Network        string // 通讯协议，默认为 tcp
	Addr           string // redis服务的地址，默认为 127.0.0.1:6379
	Database       int    // 数据库
	Username       string
	Password       string // redis鉴权密码
	ConnectTimeout time.Duration
	WriteTimeout   time.Duration
	ReadTimeout    time.Duration
	//连接池
	Wait               bool
	MaxActive          int           // 最大活动连接数，值为0时表示不限制
	MaxIdle            int           // 最大空闲连接数
	IdleTimeout        time.Duration // 空闲连接的超时时间，超过该时间则关闭连接。单位为秒。默认值是5分钟。值为0时表示不关闭空闲连接。此值应该总是大于redis服务的超时时间。
	TlsConfig          *tls.Config
	TlsSkipVerify      bool
	TestOnBorrowPeriod time.Duration

	Prefix string // 键名前缀
	//序列化
	Marshal   func(v interface{}) ([]byte, error)    `json:"-"` // 数据序列化方法，默认使用json.Marshal序列化
	Unmarshal func(data []byte, v interface{}) error `json:"-"` // 数据反序列化方法，默认使用json.Unmarshal序列化
}

// New 根据配置参数创建redis工具实例
func New(options *Options) (*RedisCache, error) {
	r := &RedisCache{}
	err := r.StartAndGC(options)
	return r, err
}

// GetAddress returns address
func (o *Options) GetAddress() string {
	return o.Addr
}
func (o *Options) GetClientName() string {
	if len(o.ClientName) == 0 {
		return defaultString
	}
	return o.ClientName
}
func (o *Options) GetUsername() string {
	if len(o.Username) == 0 {
		return defaultString
	}
	return o.Username
}

// GetPassword returns password
func (o *Options) GetPassword() string {
	if len(o.Password) == 0 {
		return defaultPassword
	}
	return o.Password
}

// GetDatabase returns database
func (o *Options) GetDatabase() int {
	if o.Database < 0 {
		return defaultDatabase
	}
	return o.Database
}

// GetNetwork returns network
func (o *Options) GetNetwork() string {
	if len(o.Network) == 0 {
		return defaultNetwork
	}
	return o.Network
}

// GetConnectTimeout returns connect timeout
func (o *Options) GetConnectTimeout() time.Duration {
	if o.ConnectTimeout < 0 {
		return defaultConnectTimeout
	}
	return o.ConnectTimeout
}

// GetWriteTimeout returns write timeout
func (o *Options) GetWriteTimeout() time.Duration {
	if o.WriteTimeout < 0 {
		return defaultWriteTimeout
	}
	return o.WriteTimeout
}

// GetReadTimeout returns read timeout
func (o *Options) GetReadTimeout() time.Duration {
	if o.ReadTimeout < 0 {
		return defaultReadTimeout
	}
	return o.ReadTimeout
}

// GetIdleTimeout returns  idle timeout
func (o *Options) GetIdleTimeout() time.Duration {
	if o.IdleTimeout < 0 {
		return defaultIdleTimeout
	}
	return o.IdleTimeout
}

// GetMaxIdle returns  max idle
func (o *Options) GetMaxIdle() int {
	if o.MaxIdle < 0 {
		return defaultMaxIdle
	}
	return o.MaxIdle
}

// GetMaxActive returns  max active
func (o *Options) GetMaxActive() int {
	if o.MaxActive < 0 {
		return defaultMaxActive
	}
	return o.MaxActive
}

// GetWait returns  wait
func (o *Options) GetWait() bool {
	return o.Wait
}

// GetTlsConfig returns tls config
func (o *Options) GetTlsConfig() *tls.Config {
	return o.TlsConfig
}

// GetTlsSkipVerify returns tls skip verify
func (o *Options) GetTlsSkipVerify() bool {
	return o.TlsSkipVerify
}

// GetTestOnBorrowPeriod return test on borrow period
func (o *Options) GetTestOnBorrowPeriod() time.Duration {
	if o.TestOnBorrowPeriod < 0 {
		return defaultTestOnBorrowTimeout
	}
	return o.TestOnBorrowPeriod
}
func NewPool(options *Options) *redis.Pool {
	return newServerPool(options)
}

func newServerPool(options *Options) *redis.Pool {
	IdleTimeout := options.GetIdleTimeout()
	MaxActive := options.GetMaxActive()
	MaxIdle := options.GetMaxIdle()
	Wait := options.GetWait()

	return &redis.Pool{
		IdleTimeout:  IdleTimeout,
		MaxActive:    MaxActive,
		MaxIdle:      MaxIdle,
		Wait:         Wait,
		Dial:         serverDial(options),
		TestOnBorrow: serverTestOnBorrow(options),
	}
}

func serverDial(options *Options) func() (redis.Conn, error) {
	network := options.GetNetwork()
	address := options.GetAddress()

	dialOptions := make([]redis.DialOption, 0)
	//dialOptions = append(dialOptions, redis.DialClientName(options.GetClientName()))
	//dialOptions = append(dialOptions, redis.DialUsername(options.GetUsername()))
	dialOptions = append(dialOptions, redis.DialPassword(options.GetPassword()))
	dialOptions = append(dialOptions, redis.DialDatabase(options.GetDatabase()))
	dialOptions = append(dialOptions, redis.DialConnectTimeout(options.GetConnectTimeout()))
	dialOptions = append(dialOptions, redis.DialWriteTimeout(options.GetWriteTimeout()))
	dialOptions = append(dialOptions, redis.DialReadTimeout(options.GetReadTimeout()))
	dialOptions = append(dialOptions, redis.DialTLSSkipVerify(options.GetTlsSkipVerify()))
	dialOptions = append(dialOptions, redis.DialTLSConfig(options.GetTlsConfig()))
	dialOptions = append(dialOptions, redis.DialTLSConfig(options.GetTlsConfig()))

	return func() (redis.Conn, error) {
		conn, err := redis.Dial(network, address, dialOptions...)
		if err != nil {
			return nil, err
		}

		if options.Password != "" {
			if options.Username != "" {
				if _, err := conn.Do("AuthACL", options.Username, options.Password); err != nil {
					conn.Close()
					return nil, err
				}
			} else {
				if _, err := conn.Do("AUTH", options.Password); err != nil {
					conn.Close()
					return nil, err
				}
			}
		}
		if _, err := conn.Do("SELECT", options.Database); err != nil {
			conn.Close()
			return nil, err
		}

		return conn, err
	}
}

func serverTestOnBorrow(options *Options) func(redis.Conn, time.Time) error {
	period := options.GetTestOnBorrowPeriod()

	return func(conn redis.Conn, t time.Time) error {
		if time.Since(t) < period {
			return nil
		}
		_, err := conn.Do(PING_COMMAND)
		return err
	}
}
